package com.birdy.ws.action;

import com.alibaba.fastjson.JSONObject;
import com.birdy.con.AppConst;
import com.birdy.dto.Response;
import com.birdy.exception.ServiceException;
import com.birdy.jwt.exception.InvalidTokenException;
import com.birdy.jwt.service.ITokenService;
import com.birdy.redis.RedisFactory;
import com.birdy.room.service.IRoomService;
import com.birdy.user.service.IUserService;
import com.birdy.vo.Room;
import com.birdy.vo.UserInfo;
import com.birdy.ws.code.MessageCode;
import com.birdy.ws.map.UserSessionMap;
import com.birdy.ws.util.SpringContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author websocket服务
 */
@ServerEndpoint("/ws/admin/{room}/{token}")
@Component
@Slf4j
public class AdministratorAction {

//    /**
//     * 记录当前在线连接数
//     */
//    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * jwt token service
     * spring管理的bean都是单例，和websocket(多对象)冲突，不能使用autowired注入。
     * @return token service
     */
    public ITokenService getTokenService() {
        return SpringContext.getBean(ITokenService.class);
    }
    public IUserService getUserService() {
        return SpringContext.getBean(IUserService.class);
    }
    public IRoomService getRoomService() {
        return SpringContext.getBean(IRoomService.class);
    }
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("token")String token, @PathParam("room") String roomId) throws ServiceException {
        System.out.println("message:"  + roomId);
        UserInfo userInfo = null;
        try {
            // 获取玩家的信息
            userInfo = getUserService().getUserInfo(getTokenService().getOpenid(token));
            // 管理员加入权限
//            userInfo.setAdmin(true);
            log.info("{}", userInfo);
        } catch (Exception e) {
            log.error("{}", e.getMessage());
        }
        // 获取在线玩家列表
        List<UserInfo> userInfos = getRoomService().getUserList(UserSessionMap.getUserSessionMap());
//        List<UserInfo> userInfos = getRoomService().getUserList(sessionMap);
        if (userInfo != null) {
            UserSessionMap.addUserSession(userInfo.getOpenId(), session);
//            sessionMap.put(userInfo.getOpenId(), session);
//            log.info("有新用户加入，openid={}, 当前在线人数为：{}", userInfo.getOpenId(), sessionMap.size());
            log.info("有新用户加入，openid={}, 当前在线人数为：{}", userInfo.getOpenId(),
                    UserSessionMap.getUserSessionMapSize());
            // 加入游戏者信息
            Response res = Response.builder().res(MessageCode.USER_LOGIN.getCode())
                    .msg(MessageCode.USER_LOGIN.getMessage())
                    .data(AppConst.KEY_USER_INFO, userInfo).build();
            // 在线玩家列表信息
            Response userInfosRes = Response.builder().res(MessageCode.USER_LIST.getCode())
                    .msg(MessageCode.USER_LIST.getMessage())
                    .data(AppConst.KEY_USER_LIST, JSONObject.toJSON(userInfos)).build();
            // 初始化在线玩家
            sendMessage(JSONObject.toJSONString(userInfosRes), session);
            // 给其他玩家发消息我加入了游戏
            sendAllMessage(JSONObject.toJSONString(res));  // 后台发送消息给所有的客户端
        } else {
            log.error("没有在线玩家");
            throw new ServiceException("userinfo not exist!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("token") String token) {
        try {
            UserInfo userInfo = getUserService().getUserInfo(getTokenService().getOpenid(token));
            UserSessionMap.removeSession(userInfo.getOpenId());
//            sessionMap.remove(userInfo.getOpenId());
//            log.info("有一连接关闭，移除openid={}的用户session, 当前在线人数为：{}", userInfo.getOpenId(), sessionMap.size());
            log.info("有一连接关闭，移除openid={}的用户session, 当前在线人数为：{}", userInfo.getOpenId(),
                    UserSessionMap.getUserSessionMapSize());
            Response res = Response.builder().res(MessageCode.USER_LOGOUT.getCode())
                    .msg(MessageCode.USER_LOGOUT.getMessage())
                    .data(AppConst.KEY_USER_INFO, userInfo).build();
            sendAllMessage(JSONObject.toJSONString(res));  // 后台发送消息给所有的客户端
        } catch (Exception e) {
            log.error("{}", e.getMessage());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * 后台收到客户端发送过来的消息
     * onMessage 是一个消息的中转站
     * 接受 浏览器端 socket.send 发送过来的 json数据
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("token") String token) {
        log.info("服务端收到用户username={}的消息:{}", token, message);
        JSONObject jsonObject = JSONObject.parseObject(message);
        UserInfo userInfo;
        try {
            userInfo = getUserService().getUserInfo(getTokenService().getOpenid(token));
            if (userInfo == null) throw new InvalidTokenException();
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            return;
        }
        Integer messageRes = jsonObject.getInteger("res");
        // 发出开始游戏指令给所有人
        if (messageRes == MessageCode.GAME_START.getCode()) {
            startGame(userInfo, jsonObject, session);
        }
        // 房间设置
        else if (messageRes == MessageCode.ROOM_SETTING.getCode()) {
            roomSetting(userInfo, jsonObject, session);
        }
        else {
            log.error("can not find message code!");
        }

    }

    private void roomSetting(UserInfo userInfo, JSONObject jsonObject, Session session) {
        Integer roomId = jsonObject.getInteger("roomId");
        Room room = getRoomService().getRoomInfo(roomId);
        if (room == null) {
            log.error("room not exist!");
            return;
        }
        if (room.getOpenid().equals(userInfo.getOpenId())) {
            String settingKey = jsonObject.getString("setting_key");
            String settingValue = jsonObject.getString("setting_value");
            RedisFactory.getRedisCommand().hset("room-" + roomId,
                    settingKey, settingValue).subscribe(
                    res -> {
                        if (res) {
                            log.info("房间号为{}的房间设置信息成功", roomId);
                            Response response = Response.builder().res(MessageCode.ROOM_SETTING.getCode())
                                    .msg(MessageCode.ROOM_SETTING.getMessage()).build();
                            sendAllMessage(JSONObject.toJSONString(response));
                        } else {
                            log.error("房间号为{}的房间设置信息失败", roomId);
                            getRoomService().deleteRoom(roomId);
                            Response response = Response.builder().res(MessageCode.ROOM_SETTING.getCode())
                                    .msg(MessageCode.ROOM_SETTING.getMessage()).build();
                            sendMessage(JSONObject.toJSONString(response), session);
                        }
                    }
            );
        } else {
            log.error("user not owner!");
        }
    }

    /**
     * 发起开始游戏请求，这个请求是管理员（法官）发起的，所以需要判断是否是管理员
     * 需要检查房间是否已经开始游戏，如果已经开始游戏，则不能再发起开始游戏请求
     * 需要检查房间人数是否达到设定的人数，如果不达到，则不能开始游戏
     * 前提条件准备好之后就可以更改redis中的数据，将房间的状态改为开始游戏
     * 并且通知map中的玩家游戏开始
     * @param userInfo 发起请求的用户信息
     * @param jsonObject
     * @param session
     */
    private void startGame(UserInfo userInfo, JSONObject jsonObject, Session session) {
        Integer roomId = jsonObject.getInteger("roomId");

        Response res = Response.builder().res(MessageCode.GAME_START.getCode())
                .msg(MessageCode.GAME_START.getMessage()).build();
        sendAllMessage(JSONObject.toJSONString(res));
    }

    @OnError
    public void onError(Session session, Throwable error) {
        if (!error.getClass().equals(ServiceException.class)) {
            log.error("", error);
        }
    }

    /**
     * 服务端发送消息给客户端
     */
    private void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }

    /**
     * 服务端发送消息给所有客户端
     */
    private void sendAllMessage(String message) {
        try {
            for (Session session : UserSessionMap.getUserSessionMap().values()) {
                log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }
    /**
     * 服务端发送消息给所有客户端
     */
    private void sendAllMessageWithoutMe(String message, String me) {
        try {
            for (String openid : UserSessionMap.getUserSessionMap().keySet()) {
                if (!openid.equals(me)) {
                    Session session = UserSessionMap.getUserSessionMap().get(openid);
                    log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
                    session.getBasicRemote().sendText(message);
                }
            }
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }
}
